/** * Basic structure: TC_Class is the public class that is returned upon being called * * So, if you do * var tc = $(".timer").TimeCircles(); * * tc will contain an instance of the public TimeCircles class. It is important to * note that TimeCircles is not chained in the conventional way, check the * documentation for more info on how TimeCircles can be chained. * * After being called/created, the public TimerCircles class will then- for each element * within it's collection, either fetch or create an instance of the private class. * Each function called upon the public class will be forwarded to each instance * of the private classes within the relevant element collection **/ (function ($) { /** * Converts hex color code into object containing integer values for the r,g,b use * This function (hexToRgb) originates from: * //stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb * @param {string} hex color code */ function hexToRgb(hex) { // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandRegex, function (m, r, g, b) { return r + r + g + g + b + b; }); var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } /** * Function s4() and guid() originate from: * //stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript */ function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } /** * Creates a unique id * @returns {String} */ function guid() { return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); } var TC_Instance_List = {}; var TC_Instance = function (element, options) { this.element = element; this.container; this.timer = null; this.data = { text_elements: { Days: null, Hours: null, Minutes: null, Seconds: null }, attributes: { //canvas: null, context: null, item_size: null, line_width: null, radius: null, outer_radius: null }, state: { fading: { Days: false, Hours: false, Minutes: false, Seconds: false } } }; this.listeners = []; this.config = null; this.setOptions(options); this.container = $("
"); this.container.addClass('time_circles'); this.container.appendTo(this.element); // this.data.attributes.canvas = $(""); //this.data.attributes.context = this.data.attributes.canvas[0].getContext('2d'); var height = this.element.offsetHeight; var width = this.element.offsetWidth; if (height === 0 && width > 0) height = width / 4; else if (width === 0 && height > 0) width = height * 4; //this.data.attributes.canvas[0].height = height; //this.data.attributes.canvas[0].width = width; //this.data.attributes.canvas.appendTo(this.container); //this.data.attributes.item_size = Math.min(this.data.attributes.canvas[0].width / 4, this.data.attributes.canvas[0].height); this.data.attributes.line_width = this.data.attributes.item_size * this.config.fg_width; this.data.attributes.radius = ((this.data.attributes.item_size * 0.8) - this.data.attributes.line_width) / 2; this.data.attributes.outer_radius = this.data.attributes.radius + 0.5 * Math.max(this.data.attributes.line_width, this.data.attributes.line_width * this.config.bg_width); // Prepare Time Elements var i = 0; for (var key in this.data.text_elements) { var textElement = $("
"); textElement.addClass('textDiv_' + key); //textElement.css("top", Math.round(0.35 * this.data.attributes.item_size)); //textElement.css("left", Math.round(i++ * this.data.attributes.item_size)); //textElement.css("width", "92px"); //textElement.css("float", "left"); textElement.appendTo(this.container); var numberElement = $(""); //numberElement.css("font-size", "24px"); //numberElement.css("font-weight", "bold"); numberElement.css("line-height", Math.round(0.07 * this.data.attributes.item_size) + "px"); numberElement.appendTo(textElement); //var headerElement = $(""); //headerElement.text(this.config.time[key].text); // Options //headerElement.css("font-size", "12px"); //headerElement.css("line-height", Math.round(0.07 * this.data.attributes.item_size) + "px"); //headerElement.appendTo(textElement); this.data.text_elements[key] = numberElement; } if (this.config.start) this.start(); }; TC_Instance.prototype.updateArc = function () { var diff, old_diff; var interval = (1000 * this.config.refresh_interval); var curDate = new Date(); // Compare current time with reference if (this.config.count_past_zero) { var prevDate = curDate - interval; diff = Math.abs(curDate - this.data.attributes.ref_date) / 1000; old_diff = Math.abs(this.data.attributes.ref_date - prevDate) / 1000; } else { diff = Math.max(this.data.attributes.ref_date - curDate, 0) / 1000; old_diff = diff + (curDate > this.data.attributes.ref_date) ? 0 : interval; } var time = { Days: (diff / 60 / 60 / 24), Hours: (diff / 60 / 60) % 24, Minutes: (diff / 60) % 60, Seconds: diff % 60 }; var pct = { Days: time.Days / 365, Hours: time.Hours / 24, Minutes: time.Minutes / 60, Seconds: time.Seconds / 60 }; var old_time = { Days: (old_diff / 60 / 60 / 24), Hours: (old_diff / 60 / 60) % 24, Minutes: (old_diff / 60) % 60, Seconds: old_diff % 60 }; var i = 0; var lastKey = null; for (var key in time) { // Set the text value this.data.text_elements[key].text(Math.floor(time[key])); var x = (i * this.data.attributes.item_size) + (this.data.attributes.item_size / 2); var y = this.data.attributes.item_size / 2; var color = this.config.time[key].color; if (Math.floor(time[key]) !== Math.floor(old_time[key])) { this.notifyListeners(key, Math.floor(time[key]), Math.floor(diff)); } // TODO: Check options for fading == true if (lastKey !== null) { if (Math.floor(time[lastKey]) > Math.floor(old_time[lastKey])) { this.radialFade(x, y, color, 1, key); this.data.state.fading[key] = true; } else if (Math.floor(time[lastKey]) < Math.floor(old_time[lastKey])) { this.radialFade(x, y, color, 0, key); this.data.state.fading[key] = true; } } if (!this.data.state.fading[key]) { // this.drawArc(x, y, color, pct[key]); } lastKey = key; i++; } }; // TC_Instance.prototype.drawArc = function (x, y, color, pct) { // var clear_radius = Math.max(this.data.attributes.outer_radius, this.data.attributes.item_size / 2); // this.data.attributes.context.clearRect( // x - clear_radius, // y - clear_radius, // clear_radius * 2, // clear_radius * 2 // ); // if (this.config.use_background) { // this.data.attributes.context.beginPath(); // this.data.attributes.context.arc(x, y, this.data.attributes.radius, 0, 2 * Math.PI, false); // this.data.attributes.context.lineWidth = this.data.attributes.line_width * this.config.bg_width; // // line color //this.data.attributes.context.strokeStyle = this.config.circle_bg_color; //this.data.attributes.context.stroke(); // } // var startAngle = (-0.5 * Math.PI); // var endAngle = (-0.5 * Math.PI) + (2 * pct * Math.PI); // var counterClockwise = false; // this.data.attributes.context.beginPath(); // this.data.attributes.context.arc(x, y, this.data.attributes.radius, startAngle, endAngle, counterClockwise); // this.data.attributes.context.lineWidth = this.data.attributes.line_width; // line color //this.data.attributes.context.strokeStyle = color; // this.data.attributes.context.stroke(); // }; TC_Instance.prototype.radialFade = function (x, y, color, from, key) { // TODO: Make fade_time option var rgb = hexToRgb(color); var _this = this; // We have a few inner scopes here that will need access to our instance var step = 0.2 * ((from === 1) ? -1 : 1); var i; for (i = 0; from <= 1 && from >= 0; i++) { // Create inner scope so our variables are not changed by the time the Timeout triggers (function () { var rgba = "rgba(" + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + (Math.round(from * 10) / 10) + ")"; setTimeout(function () { // _this.drawArc(x, y, rgba, 1); }, 50 * i); }()); from += step; } setTimeout(function () { _this.data.state.fading[key] = false; }, 50 * i); }; TC_Instance.prototype.timeLeft = function () { var now = new Date(); return ((this.data.attributes.ref_date - now) / 1000); }; TC_Instance.prototype.start = function () { // Check if a date was passed in html attribute, if not, fall back to config var attr_data_date = $(this.element).data('date'); if (typeof attr_data_date === "string") { if (attr_data_date.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{1,2}:[0-9]{2}:[0-9]{2}$/).length > 0) { attr_data_date = attr_data_date.replace(' ', 'T'); } this.data.attributes.ref_date = Date.parse(attr_data_date); } else { var attr_data_timer = $(this.element).attr('data-timer'); if (typeof attr_data_timer === "string") { this.data.attributes.timer = parseFloat(attr_data_timer); $(this.element).removeAttr('data-timer'); } else if (typeof this.config.timer === "string") { this.data.attributes.timer = parseFloat(this.config.timer); this.config.timer = null; } else if (typeof this.config.timer === "number") { this.data.attributes.timer = _this.config.timer; this.config.timer = null; } if (typeof this.data.attributes.timer === "number") { this.data.attributes.ref_date = (new Date()).getTime() + (this.data.attributes.timer * 1000); } else { this.data.attributes.ref_date = this.config.ref_date; } } // Start running var _this = this; this.timer = setInterval(function () { _this.updateArc(); }, this.config.refresh_interval * 1000); }; TC_Instance.prototype.stop = function () { if (typeof this.data.attributes.timer === "number") { this.data.attributes.timer = this.timeLeft(this); } // Stop running clearInterval(this.timer); }; TC_Instance.prototype.destroy = function () { this.stop(); this.container.remove(); $(this.element).removeData('tc-id'); }; TC_Instance.prototype.setOptions = function (options) { if (this.config === null) { this.default_options.ref_date = new Date(); this.config = $.extend(true, {}, this.default_options); } $.extend(true, this.config, options); }; TC_Instance.prototype.addListener = function (f) { if (typeof f !== "function") return; this.listeners.push(f); }; TC_Instance.prototype.notifyListeners = function (unit, value, total) { for (var i = 0; i < this.listeners.length; i++) { this.listeners[i](unit, value, total); } } TC_Instance.prototype.default_options = { ref_date: new Date(), start: true, refresh_interval: 0.1, count_past_zero: true, circle_bg_color: "#60686F", use_background: true, fg_width: 0.1, bg_width: 1.2, time: { Days: { show: true, text: "Days", color: "#FC6" }, Hours: { show: true, text: "Hours", color: "#9CF" }, Minutes: { show: true, text: "Minutes", color: "#BFB" }, Seconds: { show: true, text: "Seconds", color: "#F99" } } }; // Time circle class var TC_Class = function (elements, options) { this.elements = elements; this.options = options; this.foreach(); }; TC_Class.prototype.foreach = function (callback) { var _this = this; this.elements.each(function () { var instance; var cur_id = $(this).data("tc-id"); if (typeof cur_id === "undefined") { cur_id = guid(); $(this).data("tc-id", cur_id); } if (typeof TC_Instance_List[cur_id] === "undefined") { var element_options = $(this).data('options'); var options = _this.options; if (typeof element_options === "object") { options = $.extend(true, {}, _this.options, element_options); } instance = new TC_Instance(this, options); TC_Instance_List[cur_id] = instance; } else { instance = TC_Instance_List[cur_id]; if (typeof _this.options !== "undefined") { instance.setOptions(_this.options); } } if (typeof callback === "function") { callback(instance); } }); return this; }; TC_Class.prototype.start = function () { this.foreach(function (instance) { instance.start(); }); return this; }; TC_Class.prototype.stop = function () { this.foreach(function (instance) { instance.stop(); }); return this; }; TC_Class.prototype.addListener = function (f) { this.foreach(function (instance) { instance.addListener(f); }); return this; }; TC_Class.prototype.destroy = function () { this.foreach(function (instance) { instance.destroy(); }); return this; }; TC_Class.prototype.end = function () { return this.elements; }; $.fn.TimeCircles = function (options) { return new TC_Class(this, options); }; }(jQuery));